/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Common Public License v0.5 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v05.html
 *
 * Contributors: Boris Pruessmann - Initial implementation. David Corbin - Add
 * support for creating depot "connections" in all cases
 ******************************************************************************/
package net.sourceforge.perforce.ui.wizards;

import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IPerforceServerManager;
import net.sourceforge.perforce.core.internal.P4TeamProvider;
import net.sourceforge.perforce.core.internal.events.AddDeleteMoveListener;
import net.sourceforge.perforce.ui.PerforceDecorator;
import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.internal.Policy;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.ui.IConfigurationWizard;
import org.eclipse.ui.IWorkbench;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * This class is the starting point for the "Team->Share..." operation.
 *
 * @version $Revision: 494 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 * @author <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
 */
public class SharingWizard extends Wizard implements IConfigurationWizard {

  /** Stores a reference to the project that we want to share. */
  private IProject project;

  /** Stores the reference to the ConnectProjectPage. */
  private ConnectProjectPage connectProjectPage;

  /** Stores the tasks to be performed. */
  private List<Task> tasks;

  /** Constructor for SharingWizard. */
  public SharingWizard() {
    setNeedsProgressMonitor(true);

  }

  public void init(IWorkbench workbench, IProject project) {
    Policy.assertNotNull(project);
    this.project = project;
    connectProjectPage = new ConnectProjectPage(project.getLocation());
  }

  @Override
  public boolean canFinish() {
    if (connectProjectPage != null && !connectProjectPage.canFinish()) {
      return false;
    }
    return super.canFinish();
  }

  @Override
  public boolean performFinish() {
    if (connectProjectPage == null) {
      return true;
    } else {
      try {
        assembleTasks();
        runTasks();

        return true;
      } catch (InterruptedException e) {
        handle(e);

        return false;
      } catch (InvocationTargetException e) {
        Throwable target = e.getTargetException();
        handle(target);

        return false;
      }
    }
  }

  @Override
  public void addPages() {
    addPage(connectProjectPage);
  }

  /**
   * Call this method to assemble a list of tasks to be performed to share the
   * project.
   */
  private void assembleTasks() {
    tasks = new ArrayList<Task>();

    IPath originalLoc = getProjectLocation();
    IPath newLoc = connectProjectPage.getProjectLocation();
    if (!originalLoc.equals(newLoc)) {
      tasks.add(new MoveProject(project, connectProjectPage.getProjectLocation()));
    }
    tasks.add(new ShareProject(project, connectProjectPage.getServer()));
    tasks.add(new CacheSyncInfoTask(project, connectProjectPage.getServer()));
  }

  /**
   * Call this method to run the tasks to share the project.
   *
   * @throws InvocationTargetException in case of an error.
   * @throws InterruptedException in case of an error.
   */
  private void runTasks() throws InvocationTargetException, InterruptedException {
    Policy.assertNotNull(tasks);

    getContainer().run(false, false, new IRunnableWithProgress() {
      public void run(IProgressMonitor monitor) throws InvocationTargetException,
          InterruptedException {
        try {
          monitor.beginTask("", 1000 * tasks.size());
          for (Iterator<Task> it = tasks.iterator(); it.hasNext();) {
            Task task = it.next();
            task.run(Policy.subMonitorFor(monitor, 1000));
          }
        } finally {
          monitor.done();
        }
      }
    });
  }

  /**
   * Call this method to retrieve the project's location.
   *
   * @return the location of the project.
   */
  private IPath getProjectLocation() {
    return project.getLocation();
  }

  /**
   * Handles exceptions that occur in Perforce Sharing Wizard.
   *
   * @param t exception that should be handled.
   */
  protected void handle(Throwable t) {
    if (t instanceof InvocationTargetException) {
      t = ((InvocationTargetException) t).getTargetException();
    }

    IStatus error = null;
    if (t instanceof CoreException) {
      error = ((CoreException) t).getStatus();
    } else if (t instanceof TeamException) {
      error = ((TeamException) t).getStatus();
    } else {
      error = new Status(Status.ERROR, PerforceUIPlugin.ID, 1, Policy.bind("simpleInternal"), t); //$NON-NLS-1$
    }

    Shell shell = getContainer().getShell();

    if (error.getSeverity() == IStatus.INFO) {
      MessageDialog.openInformation(shell, Policy.bind("information"), //$NON-NLS-1$
          error.getMessage());
    } else {
      ErrorDialog.openError(shell, Policy.bind("exception"), null, error); //$NON-NLS-1$
    }

    shell.dispose();

    // Let's log non-team exceptions
    if (!(t instanceof TeamException)) {
      PerforceUIPlugin.log(error);
    }
  }

  /** Describes a Task that has to be performed to share a project. */
  private static interface Task {
    /**
     * Executes the task.
     *
     * @param monitor for progress feedback.
     * @throws InvocationTargetException in case of an error.
     * @throws InterruptedException in case of an error,
     */
    void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException;
  }

  /** @see Task */
  private static class ShareProject implements Task {

    /**
     * Stores the project instance.
     *
     * TODO: Perhaps this is better an inner class.
     */
    private IProject project;

    /** Stores the server instance. */
    private IPerforceServer server;

    /**
     * Constructor for ShareProject.
     *
     * @param project the project to share.
     * @param server Server that is used to share the project.
     */
    public ShareProject(IProject project, IPerforceServer server) {
      this.project = project;
      this.server = server;
    }

    public void run(IProgressMonitor monitor) throws InvocationTargetException {
      try {
        monitor.beginTask("", 1000);

        IPerforceServerManager manager = PerforceCorePlugin.getServerManager();
        manager.setSharing(project, server, monitor);
      } catch (TeamException e) {
        throw new InvocationTargetException(e);
      } finally {
        monitor.done();
      }
    }
  }

  /** @see Task */
  private static class MoveProject implements Task {

    /** Stores the project to move. */
    private IProject project;

    /** Stores the project's new location. */
    private IPath newLocation;

    /**
     * Constructor for MoveProject.
     *
     * @param project project to move.
     * @param newLocation the project's new location.
     */
    public MoveProject(IProject project, IPath newLocation) {
      this.project = project;
      this.newLocation = newLocation;
    }

    public void run(IProgressMonitor monitor) throws InvocationTargetException {
      try {
        monitor.beginTask("", 1000);

        IProjectDescription description = project.getDescription();
        description.setLocation(newLocation);
        project.move(description, true, monitor);
      } catch (CoreException e) {
        throw new InvocationTargetException(e);
      } finally {
        monitor.done();
      }
    }
  }

  /** Task that collects the cache information. */
  private static class CacheSyncInfoTask implements Task {

    /** Stores the project handle. */
    private IProject project;

    /** Stores the perforce server handle. */
    @SuppressWarnings("unused")
    // TODO(rcleveng): Remove this?
    private IPerforceServer server;

    /**
     * Constructor for CacheSyncInfoTask.
     *
     * @param newProject the project.
     * @param newServer the perforce server.
     */
    public CacheSyncInfoTask(IProject newProject, IPerforceServer newServer) {
      project = newProject;
      server = newServer;
    }

    public void run(IProgressMonitor monitor) {
      try {
        P4TeamProvider provider = (P4TeamProvider) RepositoryProvider.getProvider(project);

        provider.refreshState(new IResource[] {project}, IResource.DEPTH_INFINITE, monitor);

        PerforceDecorator.refresh();
        AddDeleteMoveListener.refreshAllMarkers();
      } catch (Throwable e) {
        e.printStackTrace();
      } finally {
        monitor.done();
      }
    }

  }

}
